07. Implementing Store from Scratch
We just used the createStore() function, but in order to understand it better, let's write it from scratch!
We know that we need to pass in the reducer function, and that getState() will return the current value of state. We also know that we need to be able to dispatch actions, and subscribe.
Because the subscribe() function can be called many times, we need to keep track of all the change listeners, so we create an array (listeners) that, when called, will push in the new listener to the array.
Dispatching an action is the only way to change the internal state, so we calculate the new state as the result of calling reducer() with the current state and the action being dispatched. After the state is updated, we notify every change listener by calling them.
In order to unsubscribe an event listener, we'll return a function from the subscribe() method that removes the listener from the listeners array.
By the time the store is returned, we want the initial state to be populated. We're going to dispatch a dummy action just to get the reducer to return the initial value.
const createStore = (reducer) => {let state;let listeners = [];const getState = () => state;const dispatch = (action) => {state = reducer(state, action);listeners.forEach(listener => listener());};const subscribe = (listener) => {listeners.push(listener);return () => {listeners = listeners.filter(l => l !== listener);}};dispatch({}); // dummy dispatchreturn { getState, dispatch, subscribe };};
That's pretty much it!
08. React Counter Example
In the simple example, we were updating the document body every time data in the store's state changed. Of course, this doesn't scale. So we'll use React.
Note that React has been included from CDN in the video example. We will also be rendering to <div id='root'></div>.
With React, we refactor our code a bit. We create a Counter component and render it to the root div.
Note that store.getState() is passed as a prop to the Counter element as the render function will be called any time the store's state changes.
This also allows the Counter to be refactored to a simple function (supported in react 0.14)
// ... store created with `counter` reducer ...const Counter = ({ value }) => (<h1>{value}</h1>);const render = () => {ReactDOM.render(<Counter value={store.getState()}/>,document.getElementById('root'));};store.subscribe(render);render();
We can add "Increment" and "Decrement" buttons to the Counter without re-introducing the Redux dependency, so the onIncrement/onDecrement callbacks may instead be passed as props to the button(s).
const Counter = ({value,onIncrement,onDecrement}) => (<div><h1>{value}</h1><button onClick={onIncrement}>+</button><button onClick={onDecrement}>-</button></div>);const render = () => {ReactDOM.render(<Countervalue={store.getState()}onIncrement={() =>store.dispatch({type: 'INCREMENT'})}onDecrement={() =>store.dispatch({type: 'DECREMENT'})}/>,document.getElementById('root'));}
The Counter component is a "dumb" component. It doesn't contain any business logic. A dumb component only specifies how the current state is rendered into output, and how the callbacks passed via props are bound to the event handlers.
To recap how it works
When the Counter is rendered, we specify that its value should be taken from the Redux store's current state. When the user presses a button, the corresponding action is dispatched to the Redux store.
The reducer specifies how the next state is calculated based on the current state and the action being dispatched.
Finally, we subscribe to the Redux store so our render() function runs any time the state changes so our Counter gets the current state.